package com.xsq.netty;

import com.google.gson.JsonObject;
import com.xsq.netty.bean.NettyMsg;
import com.xsq.netty.channel.INettyChannelListener;
import com.xsq.netty.utils.LogUtils;

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * 连接包装类
 * Created by Shiquan.Xiao on 2023/2/25.
 */
public class NettyConnection  {

    private final Bootstrap bootstrap;
    private Channel channel;
    private final ConnectCall connectCall = new ConnectCall();
    private final INettyChannelListener listener;
    private final ExecutorService connectPool;
    private Future<?> submitConnect;
    private final NettyConfig config;
    //连接状态，默认为连接失败状态
    private ConnectStatus status;
    private boolean haveConnection = false;
    // 是否已关闭连接和资源
    private boolean isClosed = false;

    public NettyConnection(Builder builder) {
        connectPool = builder.connectPool;
        listener = builder.listener;
        bootstrap = builder.bootstrap;
        status = ConnectStatus.FAILURE;
        config = builder.config;
    }

    public static class Builder {

        private Bootstrap bootstrap;
        private ExecutorService connectPool;
        private INettyChannelListener listener;
        private NettyConfig config;

        public Builder nettyConfig(NettyConfig config) {
            this.config = config;
            return this;
        }

        protected Builder connectPool(ExecutorService connectPool) {
            this.connectPool = connectPool;
            return this;
        }

        protected Builder bootstrap(Bootstrap bootstrap) {
            this.bootstrap = bootstrap;
            return this;
        }

        public Builder listener(INettyChannelListener listener) {
            this.listener = listener;
            return this;
        }

        public NettyConnection build() {
            return new NettyConnection(this);
        }

    }

    public void reconnect() {
        //先关闭连接
        closeConnect();
        //开始连接
        connect();
    }

    public void connect() {
        if (!haveConnection) {
            haveConnection = true;
            isClosed = false;
            if (submitConnect != null) {
                submitConnect.cancel(true);
                submitConnect = null;
            }
            submitConnect = connectPool.submit(connectCall);
        }
    }

    public boolean haveConnection() {
        return haveConnection;
    }

    public ConnectStatus getStatus() {
        return status;
    }

    public NettyConfig getConfig() {
        return config;
    }

    public INettyChannelListener getListener() {
        return listener;
    }

    public InetSocketAddress getAddress() {
        return InetSocketAddress.createUnresolved(config.host, config.port);
    }

    /**
     * 关闭channel
     */
    private void closeChannel() {
        if (channel != null) {
            try {
                removeHandler(LineBasedFrameDecoder.class.getSimpleName());
                removeHandler(StringEncoder.class.getSimpleName());
                removeHandler(StringDecoder.class.getSimpleName());
                removeHandler(NettyHeartbeatHandler.class.getSimpleName());
                removeHandler(NettyReadMsgHandler.class.getSimpleName());
                removeHandler(IdleStateHandler.class.getSimpleName());
                LogUtils.i(NettyHelper.TAG, "close channel");
            } finally {
                try {
                    channel.close();
                } catch (Exception ex) {
                    LogUtils.e(NettyHelper.TAG, "关闭channel出错，exception：" + ex.getMessage());
                }
                channel = null;
            }
        }
    }

    /**
     * 删除指定handler
     */
    private void removeHandler(String handlerName) {
        try {
            if (channel.pipeline().get(handlerName) != null) {
                channel.pipeline().remove(handlerName);
            }
        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.e(NettyHelper.TAG, "删除handler失败，exception：" + e.getMessage());
        }
    }

    /**
     * 连接服务器线程任务
     */
    private class ConnectCall implements Runnable {

        private final ChannelFutureListener futureListener = new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    addHandler();
                    LogUtils.i(NettyHelper.TAG,String.format("连接成功：『%s』:『%s』", config.host,config.port));
                    if (listener != null) {
                        listener.onConnect();
                    }
                }
            }
        };

        @Override
        public void run() {
            try {
                while (!isClosed) {
                    // 网络可用才进行连接
                    if ((status = tryReconnect()) == ConnectStatus.SUCCESSFUL) {
                        break;
                    } else {
                        if (listener != null) {
                            listener.onFailed();
                        }
                    }
                }
            } catch (Exception e) {
                LogUtils.e(NettyHelper.TAG, e.getMessage());
            }
        }

        /**
         * 尝试重连
         */
        private ConnectStatus tryReconnect() {
            if (!isClosed) {
                // 开始连接服务器
                try {
                    LogUtils.d(NettyHelper.TAG, String.format("正在进行『%s』:『%s』连接", config.host,config.port));
                    ChannelFuture future = bootstrap.connect(config.host,config.port).sync();
                    channel = future.channel();
                    future.addListener(futureListener);
                } catch (Exception e) {
                    closeChannel();
                    LogUtils.e(NettyHelper.TAG, String.format("连接服务器(ip[%s], port[%s])失败", config.host,config.port));
                }

                // 如果channel不为空，说明连接成功
                if (channel != null && channel.isActive()) {
                    return ConnectStatus.SUCCESSFUL;
                } else {
                    try {
                        Thread.sleep(config.reconnectInterval);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            return ConnectStatus.FAILURE;
        }

        /**
         * 添加心跳handler
         */
        private void addHandler() {
            if (channel != null && channel.isActive() && channel.pipeline() != null) {
                try {
                    ChannelPipeline pipeline = channel.pipeline();
                    if (pipeline.get(IdleStateHandler.class.getSimpleName()) != null) {
                        pipeline.remove(IdleStateHandler.class.getSimpleName());
                    }
                    pipeline.addFirst(IdleStateHandler.class.getSimpleName(), new IdleStateHandler(
                            config.severOnResponseInterval, config.heartbeatInterval, 0, TimeUnit.MILLISECONDS));

                    // 接收消息处理handler
                    if (pipeline.get(NettyReadMsgHandler.class.getSimpleName()) != null) {
                        pipeline.remove(NettyReadMsgHandler.class.getSimpleName());
                    }
                    pipeline.addLast(NettyReadMsgHandler.class.getSimpleName(), new NettyReadMsgHandler(NettyConnection.this));

                    if (pipeline.get(NettyHeartbeatHandler.class.getSimpleName()) != null) {
                        pipeline.remove(NettyHeartbeatHandler.class.getSimpleName());
                    }
                    pipeline.addBefore(NettyReadMsgHandler.class.getSimpleName(), NettyHeartbeatHandler.class.getSimpleName(),
                            new NettyHeartbeatHandler(NettyConnection.this));

                } catch (Exception e) {
                    LogUtils.i(NettyHelper.TAG,"添加心跳消息管理handler失败，exception：" + e.getMessage());
                }
            }
        }

    }

    public void sendMessage(JsonObject msg) {
        sendMessage(msg.toString());
    }

    public void sendMessage(String msg) {
        if (channel == null) {
            LogUtils.e(NettyHelper.TAG, "发送消息失败，没连接服务器！");
            return;
        }

        try {
            int contentLength = msg.getBytes("UTF-8").length;
            NettyMsg nettyMsg = new NettyMsg((byte) NettyHelper.MSG_TYPE, (byte) NettyHelper.MSG_FLAG, contentLength, msg);
            LogUtils.i(NettyHelper.TAG, "channel id: "+channel.id()+" content length: "+contentLength+"  发送消息: "+msg);
            channel.writeAndFlush(nettyMsg);
        } catch (Exception ex) {
            LogUtils.e(NettyHelper.TAG, "发送消息失败，exception："+ex.getMessage() + "\tmessage=" + msg);
        }
    }

    public Channel getChannel() {
        return channel;
    }

    public void closeConnect() {
        isClosed = true;
        haveConnection = false;
        NettyClient.getInstance().setConnectServer(false);
        //关闭通道
        closeChannel();
    }

    /**
     * 连接服务器状态
     */
    public enum ConnectStatus{
        // 连接中
        CONNECTING,
        // 连接成功
        SUCCESSFUL,
        // 连接失败
        FAILURE
    }

}
